use common_uu::JsonVExentd;
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::ToTokens;

#[test]
fn main() {
    println!("Hello, world!");
}

#[proc_macro_derive(MongodbSelect, attributes(orm_config))]
pub fn derive_mongodb_select(input: TokenStream) -> TokenStream {
    let input = syn::parse_macro_input!(input as syn::DeriveInput);
    let syn::DeriveInput { attrs, ident, .. } = input;
    let name = ident.to_string();

    let mut mongodb_var_name = format!("{}", "MONGO_DB");
    let mut table_name = name.to_lowercase();

    let attrs = attrs
        .iter()
        .filter(|v| {
            v.path
                .segments
                .iter()
                .find(|v| v.ident.to_string() == "orm_config")
                .is_some()
        })
        .collect::<Vec<_>>();
    if attrs.len() > 1 {
        panic!("'#[orm_config(xxx)]' defined multiple times");
    }
    if let Some(v) = attrs.get(0) {
        let json_str = v.tokens.to_string();
        let json_str = json_str
            .trim()
            .trim_start_matches('(')
            .trim_end_matches(')');
        let args = serde_json::from_str::<serde_json::Value>(json_str).unwrap_or_default();
        if let Some(v) = args["table_name"].clone().as_string2() {
            table_name = v;
        }
        if let Some(v) = args["table"].clone().as_string2() {
            table_name = v;
        }
        if let Some(v) = args["db"].clone().as_string2() {
            mongodb_var_name = v;
        }
    }

    let mongodb_var_name = Ident::new(&mongodb_var_name, Span::call_site()).into_token_stream();
    let db_collection = Ident::new(
        &format!("MONGODB_COLLECTION_SELECT_{}", name.to_uppercase()),
        Span::call_site(),
    )
    .into_token_stream();

    let var_static = quote::quote! {
        use mongodb_orm::*;

        async_static::async_static! {
            // mongodb v1.2 collection_with_type()
            // mongodb v2.0 collection()
            static ref #db_collection: mongodb::Collection<Document> = crate::#mongodb_var_name.await.collection(#table_name);
        }

    };

    let impl_select = quote::quote! {

          #[async_trait::async_trait]
          impl MongodbSelect for #ident {

            async fn collection() -> &'async_trait mongodb::Collection<Document>{
                #db_collection.await
            }

            /*async fn select_one(where_: serde_json::Map<String, serde_json::Value>) -> core::result::Result<Option<Self::T>, mongodb_orm::Error> {

                use std::convert::TryInto;
                let mut where_ : mongodb::bson::Document = where_.try_into()
                .map_err(|e: mongodb::bson::extjson::de::Error| mongodb_orm::Error::Wrapper(e.to_string()))?;

                if let Some(..) = where_.remove("$limit"){
                    Err(mongodb_orm::Error::Wrapper("select_one cannot input parameters: $limit".to_string()))?;
                }
                if let Some(..) = where_.remove("$sort"){
                    Err(mongodb_orm::Error::Wrapper("select_one cannot input parameters: $sort".to_string()))?;
                }

                debug!("select_one, db_collection: {}; where: {:?}", #db_collection.await.name(), where_);

                let r = #db_collection.await
                .find_one(where_, None).await?;

                Ok(r)
            }*/

            /*async fn select_list(where_: serde_json::Map<String, serde_json::Value>) -> core::result::Result<Vec<Self::T>, mongodb_orm::Error> {
                use std::convert::TryInto;
                use futures::TryStreamExt;

                let mut where_ : mongodb::bson::Document = where_.try_into()
                .map_err(|e: mongodb::bson::extjson::de::Error| mongodb_orm::Error::Wrapper(e.to_string()))?;

                let mut option = mongodb::options::FindOptions::default();
                if let Some(mongodb::bson::Bson::Document(v)) = where_.remove("$sort"){
                    option.sort = Some(v);
                }
                if let Some(mongodb::bson::Bson::Int64(v)) = where_.remove("$limit"){
                    option.limit = Some(v);
                }

                debug!("select_list, db_collection: {}; where: {:?}; option: {:?}", #db_collection.await.name(), where_, option);

                let list = #db_collection.await
                .find(where_, option).await?.try_collect().await?;

                Ok(list)
            }*/

            /*async fn aggregate(where_: serde_json::Map<String, serde_json::Value>) -> core::result::Result<Vec<Self::T>, mongodb_orm::Error>{
                use std::convert::TryInto;
                use futures::TryStreamExt;

                let where_ = where_.into_iter();

                let list = #db_collection.await
                .aggregate(where_, None).await?.try_collect().await?;

                Ok(list)
            }*/

        }
    };

    let stream = quote::quote! {
        // #input2
        #var_static
        #impl_select
    };

    stream.into()
}

#[proc_macro_derive(MongodbUpdate, attributes(orm_config))]
pub fn derive_mongodb_update(input: TokenStream) -> TokenStream {
    let input = syn::parse_macro_input!(input as syn::DeriveInput);

    let syn::DeriveInput { attrs, ident, .. } = input;
    let name = ident.to_string();

    let mut table_name = name.to_lowercase();
    let mut mongodb_var_name = format!("{}", "MONGO_DB");
    let mut id_str = String::from("_id");

    let attrs = attrs
        .iter()
        .filter(|v| {
            v.path
                .segments
                .iter()
                .find(|v| v.ident.to_string() == "orm_config")
                .is_some()
        })
        .collect::<Vec<_>>();
    if attrs.len() > 1 {
        panic!("'#[orm_config(xxx)]' defined multiple times");
    }
    if let Some(v) = attrs.get(0) {
        let json_str = v.tokens.to_string();
        let json_str = json_str
            .trim()
            .trim_start_matches('(')
            .trim_end_matches(')');
        let args = serde_json::from_str::<serde_json::Value>(json_str).unwrap_or_default();
        if let Some(v) = args["table_name"].clone().as_string2() {
            table_name = v;
        }
        if let Some(v) = args["table"].clone().as_string2() {
            table_name = v;
        }
        if let Some(v) = args["db"].clone().as_string2() {
            mongodb_var_name = v;
        }
        if let Some(v) = args["id"].clone().as_string2() {
            id_str = v;
        }
    }

    let _id = Ident::new(&id_str, Span::call_site()).into_token_stream();
    let mongodb_var_name = Ident::new(&mongodb_var_name, Span::call_site()).into_token_stream();
    let db_collection = Ident::new(
        &format!("MONGODB_COLLECTION_UPDATE_{}", name.to_uppercase()),
        Span::call_site(),
    )
    .into_token_stream();

    let var_static = quote::quote! {
        use mongodb_orm::*;

        async_static::async_static! {
            // mongodb v1.2 collection_with_type()
            // mongodb v2.0 collection()
            static ref #db_collection: mongodb::Collection<Document> = crate::#mongodb_var_name.await.collection(#table_name);
        }

    };

    let impl_select = quote::quote! {
        #[async_trait::async_trait]
        impl MongodbUpdate for #ident {

            async fn collection() -> &'async_trait mongodb::Collection<Document>{
                #db_collection.await
            }

            /*
            async fn insert(mut self) -> core::result::Result<String, mongodb_orm::Error> {

                // 如果id为objectId, 为mongodb自动生成, 那么设置为None
                if(#id_str == "_id"){
                    self.#id = None;
                }

                let r = #db_collection.await
                .insert_one(self, None).await?;
                let id = r.inserted_id.as_object_id().map(|v|v.to_string()).unwrap_or_default();
                Ok(id)
            }

            async fn update(&self) -> core::result::Result<(), mongodb_orm::Error> {
                let id = match &self.#id{
                    Some(v) => v,
                    None => Err(mongodb_orm::Error::Wrapper("'id' cannot be empty when updating".to_string()))?,
                };
                let query = mongodb::bson::doc! {#id_str: id};
                let mut u = mongodb::bson::to_document(self)?;

                // 如果id为objectId
                if(#id_str == "_id"){
                    let _ = u.remove(#id_str);
                }

                let r = #db_collection.await
                .update_one(query, u, None).await?;
                Ok(())
            }
            */
        }
    };

    let stream = quote::quote! {
        #var_static
        #impl_select
    };

    stream.into()
}

/*#[proc_macro_attribute]
pub fn mongodb_orm(args: TokenStream, input: TokenStream) -> TokenStream {
    let args = args.to_string();
    let input2: proc_macro2::TokenStream = input.clone().into();
    let args = serde_json::from_str::<serde_json::Value>(&args).unwrap_or_default();
    // println!("mongodb_var_name: {:?}", args);

    let mongodb_var_name = args["db"]
        .clone()
        .as_string2()
        .unwrap_or_else(|| format!("{}", "MONGO_DB"));
    let mongodb_var_name = Ident::new(&mongodb_var_name, Span::call_site()).into_token_stream();

    let id_str = args["id"]
        .as_str()
        .map(|v| v.to_string())
        .unwrap_or_else(|| String::from("_id"));
    let id = Ident::new(&id_str, Span::call_site()).into_token_stream();

    let is_only_select = args["is_only_select"].as_bool().unwrap_or_else(|| false);

    let syn::DeriveInput { ident, .. } = syn::parse(input).unwrap();
    let name = ident.to_string();

    // 表名
    let table_name = name.to_lowercase();
    let table_name = args["table_name"]
        .as_str()
        .map(|v| v.to_string())
        .unwrap_or_else(|| table_name);

    let db_collection = Ident::new(
        &format!("MONGODB_COLLECTION_{}", name.to_uppercase()),
        Span::call_site(),
    )
    .into_token_stream();

    let var_static = quote::quote! {
        use mongodb_orm::*;

        async_static::async_static! {
            // mongodb v1.2 collection_with_type()
            // mongodb v2.0 collection()
            static ref #db_collection: mongodb::Collection<#ident> = crate::#mongodb_var_name.await.collection_with_type::<#ident>(#table_name);
        }

    };

    let impl_insert = if is_only_select {
        quote::quote! {}
    } else {
        quote::quote! {
            #[async_trait::async_trait]
            impl MongodbInsert for #ident {
                async fn insert(mut self) -> core::result::Result<String, mongodb_orm::Error> {

                    // 如果id为objectId, 为mongodb自动生成, 那么设置为None
                    if(#id_str == "_id"){
                        self.#id = None;
                    }

                    let r = #db_collection.await
                    .insert_one(self, None).await?;
                    let id = r.inserted_id.as_object_id().map(|v|v.to_string()).unwrap_or_default();
                    Ok(id)
                }

                async fn update(&self) -> core::result::Result<(), mongodb_orm::Error> {
                    let id = match &self.#id{
                        Some(v) => v,
                        None => Err(mongodb_orm::Error::Wrapper("'id' cannot be empty when updating".to_string()))?,
                    };
                    let query = mongodb::bson::doc! {#id_str: id};
                    let mut u = mongodb::bson::to_document(self)?;

                    // 如果id为objectId
                    if(#id_str == "_id"){
                        let _ = u.remove(#id_str);
                    }

                    let r = #db_collection.await
                    .update_one(query, u, None).await?;
                    Ok(())
                }
            }
        }
    };

    /*let impl_select = quote::quote! {

          #[async_trait::async_trait]
          impl MongodbSelect for #ident {

            type T = #ident;

            async fn select_one(where_: serde_json::Map<String, serde_json::Value>) -> core::result::Result<Option<Self::T>, mongodb_orm::Error> {

                use std::convert::TryInto;
                let mut where_ : mongodb::bson::Document = where_.try_into()
                .map_err(|e: mongodb::bson::extjson::de::Error| mongodb_orm::Error::Wrapper(e.to_string()))?;

                if let Some(..) = where_.remove("$limit"){
                    Err(mongodb_orm::Error::Wrapper("select_one cannot input parameters: $limit".to_string()))?;
                }
                if let Some(..) = where_.remove("$sort"){
                    Err(mongodb_orm::Error::Wrapper("select_one cannot input parameters: $sort".to_string()))?;
                }

                let r = #db_collection.await
                .find_one(where_, None).await?;

                Ok(r)
            }

            async fn select_list(where_: serde_json::Map<String, serde_json::Value>) -> core::result::Result<Vec<Self::T>, mongodb_orm::Error> {
                use std::convert::TryInto;
                use futures::TryStreamExt;

                let mut where_ : mongodb::bson::Document = where_.try_into()
                .map_err(|e: mongodb::bson::extjson::de::Error| mongodb_orm::Error::Wrapper(e.to_string()))?;

                let mut option = mongodb::options::FindOptions::default();
                if let Some(mongodb::bson::Bson::Document(v)) = where_.remove("$sort"){
                    option.sort = Some(v);
                }
                if let Some(mongodb::bson::Bson::Int64(v)) = where_.remove("$limit"){
                    option.limit = Some(v);
                }

                let list = #db_collection.await
                .find(where_, option).await?.try_collect().await?;

                Ok(list)
            }
        }
    };*/

    let stream = quote::quote! {
        // #input2
        // #var_static
        // #impl_select
        // #impl_insert
    };

    stream.into()
}*/
